#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <math.h>
#include <ctype.h>

#include "structure.h"
#include "config.h"
#include "./DEV/table_fichiers.h"
#include "./DEV/partition/buffermap.h"
#include "./DEV/partition/data_query.h"
#include "getpieces.h"
#include "test_getpiece_query.h"

#define PIECE_SIZE 2048

extern struct database db; 

int get_file_by_key(char * key){
  int file;
  for (file = 0 ; file < db.nb_file ; file++){
    if (strncmp(key,db.tab_file[file].key,32) == 0)
      break;
  }
  if (file == db.nb_file){
    fprintf(stderr,"Key not found\n");
    return -1;
  }
  return file;
}


void write_buffer(char * buffer, TableFichiers d, int portno){
  sprintf(buffer, "announce listen %d ", portno);
  int i;
  char  buf[5000], buff_seed[5000];
   bzero(buff_seed,5000);
  for (i=0 ; i < d.fin_table ; i++){
    sprintf(buf, "%s %ld %d %s ", d.contenu[i].nom_fichier, d.contenu[i].taille, PIECE_SIZE, d.contenu[i].clef);
    strcpy(db.tab_file[db.nb_file].name, d.contenu[i].nom_fichier);
    db.tab_file[db.nb_file].size = d.contenu[i].taille;
    db.tab_file[db.nb_file].piece_size = PIECE_SIZE;
    strcpy(db.tab_file[db.nb_file].key, d.contenu[i].clef);
    db.tab_file[db.nb_file].type = SEED; 
    db.tab_file[db.nb_file].buffer_map = pow(2,ceil((double)db.tab_file[db.nb_file].size/db.tab_file[db.nb_file].piece_size))-1;
    db.tab_file[db.nb_file].clients = NULL;
    db.nb_file++;
    strcat(buff_seed,buf);
    }

  strcat(buffer, "seed [ ");
  strcat(buffer, buff_seed);
  strcat(buffer, "]");
  strcat(buffer, " leech [ ");
  strcat(buffer, "]\n");
}


char * select_command(char * buffer, char * IP){
  if(strncmp(buffer,"interested",10) == 0){
    char key[32];
    int i;
    for(i = 11 ; i < 43 ; i++)
      key[i-11] = buffer[i];
   
    int file = get_file_by_key(key);
    if (file == -1){
      return NULL;
    }

    buffermap buffermap = create_buffermap(db.tab_file[file].buffer_map, db.tab_file[file].size);
    char * respond = malloc(38+buffermap.size+1);
    strcpy(respond,"have ");
    strncat(respond,key,32);
    strcat(respond," ");
    strcat(respond,buffermap.buffer);
    strcat(respond,"\0");
    
    return respond;
  }
  else if(strncmp(buffer,"getpieces ",10) == 0){
    printf("buffer = %s\n",buffer);
    char key[32];
    int i;
    for(i = 10 ; i < 42 ; i++){
      if(buffer[i] == ' '){
	fprintf(stderr,"ERROR, non-complet given key.\n");
	return NULL;
      }
      key[i-10] = buffer[i];
    }
    int file = get_file_by_key(key);
    if (file == -1){
      return NULL;
    }
    i++;
    i++;
    int iter = 0;
    int pieces[10];
    if (buffer[i] == ']')
      return NULL;
    while(buffer[i] != ']'){
      char number[6];
      int k = 0;
      while(isdigit(buffer[i])){
	number[k] = buffer[i];
	k++;
	i++;
      }
      number[k]='\0';
      pieces[iter]=atoi(number);
      iter++;
      if (buffer[i] == ']')
	break;
      i++;
    }
    char *respond = malloc(QUERY_SIZE);
    _t_buffermap bfmp = intBufToBM(pieces, iter, 0);
    data_query(respond, file, bfmp);

    return respond;
  }
  
  else if(strncmp(buffer,"have ",5) == 0){
    char key[32];
    int i;
    for(i = 5 ; i < 37 ; i++){
      if(buffer[i] == ' '){
	fprintf(stderr,"ERROR, non-complet given key.\n");
	return NULL;
      }
      key[i-5] = buffer[i];
    }
    int file = get_file_by_key(key);
    if (file == -1){
      return NULL;
    }
    buffermap buffmap;
    
    buffmap.size = ceil((double)db.tab_file[file].size/db.tab_file[file].piece_size);
    
    char *tmp = malloc(buffmap.size*sizeof(char)+1);
    
    buffmap.buffer = tmp;
    
    int k;
    
    for(k=0 ; k<buffmap.size ; k++){
      buffmap.buffer[k] = buffer[i];
      i++;
    }
    buffmap.buffer[k] = '\0';
    long long map_long = buffmap_to_int(buffmap);
    
    struct client * cp = db.tab_file[file].clients;
    if(cp != NULL){
      while(cp != NULL) {
	if (strcmp(IP,cp->IP) == 0)
	  break;
	cp = cp->next;
      }
      
      cp->client_buffer_map = map_long;
    }
    free(tmp);
    
    buffermap buffermap = create_buffermap(db.tab_file[file].buffer_map, db.tab_file[file].size);
    
    char * respond = malloc(38+buffermap.size+1);
    strcpy(respond,"have ");
    strncat(respond,key,32);
    strcat(respond," ");
    strcat(respond,buffermap.buffer);
    strcat(respond,"\0");
    
    return respond;
    
  }
  else
    {
      fprintf(stderr,"ERROR, request not recognized\n");
      return NULL;
    }
}


void read_write_buff(int *newsockfd){
  int n;
  char buffer[500];
  bzero(buffer,500);
  n = 1;

  n = read(*newsockfd,buffer,500);
  printf("n = %d\n",n);
  if (n < 0) 
    fprintf(stderr,"ERROR reading from socket");
  
  printf("%s\n",buffer);
  
  struct sockaddr_in addr;
  unsigned int size = sizeof(addr);
  getpeername(*newsockfd,(struct sockaddr*)&addr,&size);
  char IP[15];
  strcpy(IP,inet_ntoa(addr.sin_addr));
  char * respond = select_command(buffer,IP);
  
  if (respond == NULL){
    fprintf(stderr,"ERROR command treatment\n");
  }
  else
    {
      n = write(*newsockfd,respond,QUERY_SIZE);
      if (n < 0) 
	fprintf(stderr,"ERROR writing to socket");  
      //      free(respond);
    }
}


void receive_msg(char **argv){
  int sockfd, newsockfd, portno, clilen;
  struct sockaddr_in serv_addr, cli_addr;
  
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) 
    fprintf(stderr,"ERROR opening socket");
  bzero((char *) &serv_addr, sizeof(serv_addr));
  portno = atoi(argv[1]);
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(portno);
  if (bind(sockfd, (struct sockaddr *) &serv_addr,
	   sizeof(serv_addr)) < 0) 
    fprintf(stderr,"ERROR on binding");
  listen(sockfd,5);
  clilen = sizeof(cli_addr);

  pthread_t tabThread[Max_Connection];
  int i=0;

  while(1){
    newsockfd = accept(sockfd, 
		       (struct sockaddr *) &cli_addr, 
		       &clilen);
    if (newsockfd < 0) {
      fprintf(stderr,"ERROR on accept");
      break;
    }

    if (pthread_create(&tabThread[i],NULL,(void*)read_write_buff,&newsockfd) !=0)
      fprintf(stderr,"ERROR creating thread.\n");
    i++;  
  }   
}


void send_msg(args * arglist){
  int sockfd;
  struct sockaddr_in serv_addr;
  struct hostent *server;

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) 
    fprintf(stderr,"ERROR opening socket");
  server = gethostbyname(arglist->IP);
  if (server == NULL) {
    fprintf(stderr,"ERROR, no such host\n");
    return;
  }

  bzero((char *) &serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  bcopy((char *)server->h_addr, 
	(char *)&serv_addr.sin_addr.s_addr,
	server->h_length);
  serv_addr.sin_port = htons(arglist->portno);
  
  
  if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) {
    fprintf(stderr,"ERROR connecting.\n");
    return;
  }
  else
    printf("Connection established.\n");
  int wt = write(sockfd,arglist->buffer,strlen(arglist->buffer)) ;
  if( wt < 0)
    fprintf(stderr,"ERROR writing to socket.\n");

  printf("%s\n",arglist->buffer);

  bzero(arglist->buffer,strlen(arglist->buffer));
  char buff[MAX_LENGTH];
  int rd = recv(sockfd,buff,MAX_LENGTH-1,0);
  if(rd < 0)
    fprintf(stderr,"ERROR reading from socket.\n");
  strncat(arglist->buffer,buff,rd);
  arglist->buffer[rd] = '\0';
  //traitement de la reception
  arglist->func(arglist->buffer);
  free(arglist);
}

void send_msg_bis(args_bis * arglist){
  int sockfd;
  struct sockaddr_in serv_addr;
  struct hostent *server;
  
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0) 
    fprintf(stderr,"ERROR opening socket");
  server = gethostbyname(arglist->IP);
  if (server == NULL) {
    fprintf(stderr,"ERROR, no such host\n");
    return;
  }

  bzero((char *) &serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  bcopy((char *)server->h_addr, 
	(char *)&serv_addr.sin_addr.s_addr,
	server->h_length);
  serv_addr.sin_port = htons(arglist->portno);
  
  
  if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0){ 
    fprintf(stderr,"ERROR connecting.\n");
    return;
  }
  else
    printf("Connection established.\n");

  if(write(sockfd,arglist->buffer,strlen(arglist->buffer)) < 0)
    fprintf(stderr,"ERROR writing to socket");
  printf("%s\n",arglist->buffer);
  char buff[MAX_LENGTH];
  int rd = read(sockfd,buff,MAX_LENGTH-1);
  if(rd < 0)
    fprintf(stderr,"ERROR reading from socket");
  //traitement de la reception
  buff[rd]='\0';
  arglist->func(buff,arglist->IP);
  free(arglist);
}


void print(char * buff){
  printf("%s\n",buff);
}

void look_func(char * buff){
  int ji = strncmp(buff,"list [",6);
  if( ji != 0){
    fprintf(stderr,"ERROR receiving look respond\n");
    return;
  }
  int i=6;
  int j=0;
  
  while(buff[i] != ']'){  
    char name[32];
    while(buff[i] != ' '){
      if (buff[i] == ']'){
	fprintf(stderr,"ERROR non-complete message\n");
	return;
      }
      name[j] = buff[i];
      i++;
      j++;
    }
    i++;
    name[j] = '\0';
    
    char size[50];
    j = 0;
    while(buff[i] != ' '){
      if (buff[i] == ']'){
	fprintf(stderr,"ERROR non-complete message\n");
	return;
      }
      size[j] = buff[i];
      i++;
      j++;
    }
    i++;

    char piece_size[50];
    j = 0;
    while(buff[i] != ' '){
      if (buff[i] == ']'){
	fprintf(stderr,"ERROR non-complete message\n");
	return;
      }
      piece_size[j] = buff[i];
      i++;
      j++;
    }
    i++;

    char key[32];
    j = 0;
    for(j = 0 ; j<32 ; j++){
      if (buff[i] == ']'){
	fprintf(stderr,"ERROR non-complete message\n");
	return;
      }
      key[j] = buff[i];
      i++;
    }
   
    int file = get_file_by_key(key);
    if (file != -1){
      fprintf(stderr,"The file already seen.\n");
      return;
    }
    
    db.tab_file[db.nb_file].piece_size = atoi(piece_size);    
    db.tab_file[db.nb_file].size = atoi(size);
    strcpy(db.tab_file[db.nb_file].name, name);
    strncpy(db.tab_file[db.nb_file].key, key,32);
    db.tab_file[db.nb_file].clients = NULL;
    db.tab_file[db.nb_file].type = INTERESTED;
    db.tab_file[db.nb_file].buffer_map = 0;
    db.nb_file++;
  }

  printf("%s\n",buff);
  
}


void interest_func(char *buff, char * IP){
  char bf[5];
  int i;
  for(i=0 ; i<5 ; i++)
    bf[i] = buff[i];
  int ji = strncmp(bf,"have ",5);
  if( ji != 0){
    fprintf(stderr,"ERROR receiving getfile respond\n");
    return;
  }
  
  int cmp = 0;
  char key[32];
  while(buff[i] != ' '){  
    key[cmp] = buff[i];
    cmp++;
    i++;
  }
  if (cmp != 32){
    fprintf(stderr,"ERROR non-complete key\n");
    return;
  }
  
  int file = get_file_by_key(key);
  if (file == -1){
    return;
  }
  
  i++;
  
  printf("%s\n",buff);

  buffermap buffmap;

  buffmap.size = ceil((double)db.tab_file[file].size/db.tab_file[file].piece_size);
  
  char *tmp = malloc(buffmap.size*sizeof(char)+1);

  buffmap.buffer = tmp;
  
  int k;
  
  for(k=0 ; k<buffmap.size ; k++){
    buffmap.buffer[k] = buff[i];
    i++;
  }
  buffmap.buffer[k] = '\0';
  long long map_long = buffmap_to_int(buffmap);

  struct client * cp = db.tab_file[file].clients;
  while(cp != NULL) {
    if (strcmp(IP,cp->IP) == 0)
      break;
    cp = cp->next;
  }
  
  cp->client_buffer_map = map_long;
  free(tmp);
  
  pthread_t send_peaces;
  if (pthread_create(&send_peaces,NULL,(void*)get_pieces_func,&file) !=0)
    fprintf(stderr,"ERROR creating thread.\n");
}


void getfile_func(char * buff){
  char bf[6];
  int i;
  for(i=0 ; i<6 ; i++)
    bf[i] = buff[i];
  int ji = strncmp(bf,"peers ",6);
  if( ji != 0){
    fprintf(stderr,"ERROR receiving getfile respond\n");
    return;
  }
  
  int cmp = 0;
  char tmp[32];
  while(buff[i] != ' '){  
    tmp[cmp] = buff[i];
    cmp++;
    i++;
  }
  char key[32];
  strncpy(key,tmp,32);
  if (cmp != 32){
    fprintf(stderr,"ERROR non-complete key\n");
    return;
  }
  int file = get_file_by_key(key);
  if (file == -1){
    return;
  }
  
  i++;
  i++;
  while(1){
    char * ip_adr = malloc(16);
    char port[10];
    int j = 0;
    
    while(buff[i] != ':'){
      ip_adr[j] = buff[i];
      i++;
      j++;
    }
    ip_adr[j] = '\0';
    j = 0;
    i++;
    while((buff[i] != ']') && (buff[i] != ' ')){
      port[j] = buff[i];
      i++;
      j++;
    }

    struct client *new = malloc(sizeof(struct client));
    strcpy(new->IP,ip_adr);
    new->port = atoi(port);
    new->next = NULL;
    new->downloading = 0;
    pthread_mutex_init(&new->mutex,NULL);
    
    
    struct client *cl = db.tab_file[file].clients;
    if (cl == NULL){
      db.tab_file[file].clients = new;
    }
    else{
      while(cl->next != NULL){
	cl=cl->next;
      }
      cl->next = new;
    }
    
    printf("%s\n",buff);
    free(ip_adr);
    
    char buffer[50];
    strcpy(buffer,"interested ");
    strncat(buffer,key,32);
    //strcat(buffer,"\0");
    
    args_bis* arglist = malloc(sizeof(args));
    arglist->portno = new->port;
    arglist->IP = new->IP;
    arglist->buffer = buffer;
    arglist->func = interest_func;
    
    pthread_t Thread_connection;
    
    if (pthread_create(&Thread_connection,NULL,(void*)send_msg_bis,arglist) != 0)
      fprintf(stderr,"ERROR creating thread.\n");
    pthread_join(Thread_connection,NULL);
    if (buff[i] == ']') break;
    i++;
  }
}




int get_client_query(){

  printf("Select the number of the command:\n1 - look\n2 - getfile\n3 - Exit the program\n");

  int i;
  char buffer[MAX_LENGTH];
  char buff[MAX_LENGTH];
  pthread_t Thread_connection;    

  scanf("%d",&i);

  switch(i){

  case(1):
    printf("Command \"look\" :\nFile name:");
    scanf("%s",buff);

    strcpy(buffer,"look [filename=\"");
    strcat(buffer,buff);
    strcat(buffer,"\"] ");
    
    args* arglist = malloc(sizeof(args));
    arglist->argv = 0;
    arglist->portno = NO_PORT_TRACKER;
    arglist->IP = IP_TRACKER;
    arglist->buffer = buffer;
    arglist->func = look_func;

    

    if (pthread_create(&Thread_connection,NULL,(void*)send_msg,arglist) !=0)
      fprintf(stderr,"ERROR creating thread.\n");

    if (pthread_join(Thread_connection, NULL) !=0)
      fprintf(stderr,"ERROR executing thread.\n");
    
    return 0;
    break;


  case(2):
    printf("Command \"getfile\" :\nFile key:");
    scanf("%s",buff);

    strcpy(buffer,"getfile ");
    strcat(buffer,buff);
    strcat(buffer," ");
    
    args* arglist2 = malloc(sizeof(args));
    arglist2->argv = 0;
    arglist2->portno = NO_PORT_TRACKER;
    arglist2->IP = IP_TRACKER;
    arglist2->buffer = buffer;
    arglist2->func = getfile_func;

    if (pthread_create(&Thread_connection,NULL,(void*)send_msg,arglist2) !=0)
      fprintf(stderr,"ERROR creating thread.\n");

    if (pthread_join(Thread_connection, NULL) !=0)
      fprintf(stderr,"ERROR executing thread.\n");
    
    return 0;
    break;
    
  case(3):
    return 1;

  default:
    printf("Number selected not correct\n");
    break;
  }
  return -1;
}


int open_connect(char * ip, int port){
   int sockfd;
   struct sockaddr_in serv_addr;
   struct hostent *server;

   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   if (sockfd < 0){
      fprintf(stderr,"ERROR opening socket");
      return -1;
      }

   server = gethostbyname(ip);

   if (server == NULL) {
      fprintf(stderr,"ERROR, no such host\n");
      return -1;
   }

   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET;
   bcopy((char *)server->h_addr, 
         (char *)&serv_addr.sin_addr.s_addr,
         server->h_length);
   serv_addr.sin_port = htons(port);

   if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0){
      fprintf(stderr,"ERROR connecting.\n");
      return -1;
   }
   else
      printf("Connection established.\n");

   return sockfd;
}
